/***************************Copyright-DO-NOT-REMOVE-THIS-LINE**
 *
 * Condor Software Copyright Notice
 * Copyright (C) 1990-2004, Condor Team, Computer Sciences Department,
 * University of Wisconsin-Madison, WI.
 *
 * This source code is covered by the Condor Public License, which can
 * be found in the accompanying LICENSE.TXT file, or online at
 * www.condorproject.org.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * AND THE UNIVERSITY OF WISCONSIN-MADISON "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, OF SATISFACTORY QUALITY, AND FITNESS
 * FOR A PARTICULAR PURPOSE OR USE ARE DISCLAIMED. THE COPYRIGHT
 * HOLDERS AND CONTRIBUTORS AND THE UNIVERSITY OF WISCONSIN-MADISON
 * MAKE NO MAKE NO REPRESENTATION THAT THE SOFTWARE, MODIFICATIONS,
 * ENHANCEMENTS OR DERIVATIVE WORKS THEREOF, WILL NOT INFRINGE ANY
 * PATENT, COPYRIGHT, TRADEMARK, TRADE SECRET OR OTHER PROPRIETARY
 * RIGHT.
 *
 ****************************Copyright-DO-NOT-REMOVE-THIS-LINE**/
/* These methods will be implemented to reflect the application behavior */

#include <unistd.h>
#include <math.h>
#include <iostream>
#include <MW.h>
#include <png.h>
#include <boost/program_options.hpp>
#include <boost/current_function.hpp>

#include <image.h>
#include <config.h>

#include "MWFDriver.h"
#include "MWFWorker.h"
#include "MWFTask.h"

#ifdef INDEPENDENT
	#define MAX_BUF_SIZE 1024*1024*64
#else
	#define MAX_BUF_SIZE 1024*1024*16
#endif
namespace po = boost::program_options;

/* initialization */
MWFDriver::MWFDriver() {
	MWprintf(50, "Enter: %s\n", BOOST_CURRENT_FUNCTION);
	MWprintf(50, "Left: %s\n", BOOST_CURRENT_FUNCTION);
}

/* destruction */
MWFDriver::~MWFDriver() {
	MWprintf(50, "Enter: %s\n", BOOST_CURRENT_FUNCTION);

	if (image_rows) {
		for (int y = 0; y < img_height; y++) {
			delete[] image_rows[y];
		}
		delete[] image_rows;
	}

	if (foutput) {
		delete foutput;
	}
	MWprintf(50, "Left: %s\n", BOOST_CURRENT_FUNCTION);

}

/* Here the application can (1) get per-run information from stdin (the input
 * file redirected by Condor), or (2) get configuration info from user-defined
 * config file; or (3) just hard-code your config here. The app should tell MW
 * (or RMC the Resource Manager and Communicator, which is a class member)
 * some basic info by calling the functions below:
 * (a) RMC->set_num_exec_classes()      num of different processes you have
 * (b) RMC->set_num_arch_classes() 	num of different platforms you have
 * (c) RMC->set_arch_class_attributes()  	for each platform
 * (d) RMC->add_executable() 	for combination of exec_class, arch_class;
 * (e) set_checkpoint_freqency(), ... and other information.  */

MWReturn MWFDriver::get_userinfo(int argc, char *argv[]) {
	MWprintf(50, "Enter: %s\n", BOOST_CURRENT_FUNCTION);

	//	int num_exec = 1;
	//	int num_arch = 1;

	po::options_description generic("Generic options");
	generic.add_options()("help", "produce help message")("width,w", po::value<
			int>()->default_value(800), "image width")("height,h", po::value<
			int>()->default_value(600), "image height")("max-iteration,m",
			po::value<int>()->default_value(250), "maximum iterations")(
			"scale,s", po::value<int>()->default_value(100), "scale")(
			"num-segments", po::value<int>()->default_value(4),
			"number of segments the computation should be split in both axes")(
			"worker-executable", po::value<std::string>(), "worker executable");

	po::options_description hidden("Hidden options");
	hidden.add_options()("output", po::value<std::string>(), "output filename");

	po::positional_options_description pgeneric;
	pgeneric.add("output", 1);

	po::variables_map args;
	po::store(po::command_line_parser(argc, argv). options(
			po::options_description().add(generic).add(hidden)).positional(
			pgeneric).run(), args);
	po::notify(args);

	if (args.count("help")) {
		std::cout << "Usage: %s [OPTIONS]... <output-file>" << std::endl
				<< std::endl;
		std::cout << generic << std::endl;
		return ABORT;
	}

	if (args.count("output") != 1) {
		MWprintf(10, "Error: output file missing\n");
		return ABORT;
	} else {
		this->foutput = strdup(args["output"].as<std::string> ().c_str());
	}

	if (args.count("worker-executable") != 1) {
		MWprintf(10, "Error: worker executable file missing\n");
		return ABORT;
	} else {
		this->worker_executable = strdup(args["worker-executable"].as<
				std::string> ().c_str());
	}

	this->num_segments = args["num-segments"].as<int> ();
	this->num_images = num_segments * num_segments;
	this->img_width = args["width"].as<int> ();
	this->img_height = args["height"].as<int> ();
	this->img_scale = args["scale"].as<int> ();
	this->max_itererations = args["max-iteration"].as<int> ();

	if (img_height * img_width * 4 / num_images > MAX_BUF_SIZE) {
		MWprintf(10, "Error: size too big - maximum size is %d\n", MAX_BUF_SIZE
				/ 4);
		return ABORT;
	}

	// print summary
	MWprintf(
			10,
			"Ready to go: output %s, %dx%d, scale: %d, max-iterations:%d split into %d segments worker: %s\n",
			foutput, img_width, img_height, img_scale, max_itererations,
			num_segments, worker_executable);

	/* exec classes */
	//RMC->set_num_exec_classes(num_exec);

	/* arch classes */
	//RMC->set_num_arch_classes(num_arch);

	/* executables */
	//RMC->set_num_executables(num_exec);

	// TODO: extrect the full executable name
	RMC->add_executable(worker_executable,
			"((OPSYS==\"LINUX\")&&(ARCH==\"INTEL\"))");
	/* checkpoint requirement */
	set_checkpoint_frequency(0);

	this->image_rows = new png_bytep[img_height];
	for (int i = 0; i < img_height; i++) {
		this->image_rows[i] = NULL;
	}

	RMC->set_target_num_workers(num_images);

	MWprintf(50, "Left: %s\n", BOOST_CURRENT_FUNCTION);

	return OK;
}

/* setup (generate and push) the first batch of tasks in the beginning */
MWReturn MWFDriver::setup_initial_tasks(int *n_init, MWTask ***init_tasks) {
	MWprintf(50, "Enter: %s\n", BOOST_CURRENT_FUNCTION);
	*n_init = num_images;
	*init_tasks = new MWTask *[num_images];

	int pwidth = img_width / num_segments;
	int pheight = img_height / num_segments;
	int part = 0;

	for (int y = 0; y < num_segments; y++) {
		for (int x = 0; x < num_segments; x++) {
			double centerx = x * pwidth + pwidth / 2.0 - img_width / 2.0;
			double centery = y * pheight + pheight / 2.0 - img_height / 2.0;

			double xmin = (centerx - pwidth / 2.0) / img_scale;
			double ymin = (centery - pheight / 2.0) / img_scale;
			double xmax = (centerx + pwidth / 2.0) / img_scale;
			double ymax = (centery + pheight / 2.0) / img_scale;

			RenderingOptions options;
			options.width = pwidth;
			options.height = pheight;
			options.xmin = xmin;
			options.xmax = xmax;
			options.ymin = ymin;
			options.ymax = ymax;
			options.max_iterations = max_itererations;

			(*init_tasks)[part] = new MWFRenderFractalTask(part, options);

			part++;
		}
	}
	MWprintf(50, "Left: %s\n", BOOST_CURRENT_FUNCTION);

	return OK;
}

/* Implement application behavior to process a just completed task */
MWReturn MWFDriver::act_on_completed_task(MWTask *t) {
	MWprintf(50, "Enter: %s\n", BOOST_CURRENT_FUNCTION);

#ifdef NO_DYN_CAST
	MWFRenderFractalTask *taks = (MWFRenderFractalTask*) t;
#else
	MWFRenderFractalTask *task = dynamic_cast<MWFRenderFractalTask *> (t);
#endif

	int sy = img_height / num_segments * (task->part / num_segments);
	int sx = img_width / num_segments * (task->part % num_segments);

	for (int y = 0; y < task->image->height; y++) {
		if (image_rows[sy + y] == NULL) {
			image_rows[sy + y] = new png_byte[img_width * 4];
		}

		for (int x = 0; x < task->image->width; x++) {
			image_rows[sy + y][(sx + x) * 4 + 0] = rgb_red(task->image->data[y][x]);
			image_rows[sy + y][(sx + x) * 4 + 1] = rgb_green(task->image->data[y][x]);
			image_rows[sy + y][(sx + x) * 4 + 2] = rgb_blue(task->image->data[y][x]);
			image_rows[sy + y][(sx + x) * 4 + 3] = rgb_alpha(task->image->data[y][x]);
		}
	}

	MWprintf(30, "Processed part: %d", task->part);
	MWprintf(50, "Left: %s\n", BOOST_CURRENT_FUNCTION);
	return OK;
}

/* The first batch of data for a newly spawned worker, e.g. init data */
MWReturn MWFDriver::pack_worker_init_data(void) {
	MWprintf(50, "Enter: %s\n", BOOST_CURRENT_FUNCTION);
	MWprintf(50, "Left: %s\n", BOOST_CURRENT_FUNCTION);
	/* Nothing for this application */
	return OK;
}

/* Print out the result when MW is done. MW assume that the application
 * is keeping track of the results :-) */
void MWFDriver::printresults() {
	// TODO: check for errors
	MWprintf(50, "Enter: %s\n", BOOST_CURRENT_FUNCTION);

	png_byte color_type;
	png_byte bit_depth;
	png_structp png_ptr;
	png_infop info_ptr;
	color_type = PNG_COLOR_TYPE_RGBA;
	bit_depth = 8;
	FILE *fp = fopen(foutput, "wb");
	png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	info_ptr = png_create_info_struct(png_ptr);
	png_init_io(png_ptr, fp);

	png_set_IHDR(png_ptr, info_ptr, img_width, img_height, bit_depth,
			color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
			PNG_FILTER_TYPE_BASE);

	png_write_info(png_ptr, info_ptr);
	png_write_image(png_ptr, image_rows);
	png_write_end(png_ptr, NULL);
	fclose(fp);

	MWprintf(50, "Left: %s\n", BOOST_CURRENT_FUNCTION);
}

/* Write app-specific master checkpoint info */
void MWFDriver::write_master_state(FILE *fp) {
	/* Nothing to be written */
}

/* Read app-specific master checkpoint info */
void MWFDriver::read_master_state(FILE *fp) {
	/* Nothing to be read */
}

/* Return a new application task object */
MWTask*
MWFDriver::gimme_a_task() {
	MWprintf(50, "Enter: %s\n", BOOST_CURRENT_FUNCTION);
	MWprintf(50, "Left: %s\n", BOOST_CURRENT_FUNCTION);
	return new MWFRenderFractalTask;
}

/* Return a new driver object */
MWDriver* gimme_the_master() {
	MWprintf(50, "Enter: %s\n", BOOST_CURRENT_FUNCTION);
	MWprintf(50, "Left: %s\n", BOOST_CURRENT_FUNCTION);
	return new MWFDriver;
}
